﻿static char *msgmng_id = "@(#)Copyright (C) gaoguangtao   msgmng.cpp	Ver2.06";

//Socket管理源文件
/* ========================================================================
	Project  Name			: IP Messenger for Win32
	Module Name				: Message Manager
	Create					: 2008-02-20(Sat)
    Update					: 2008-02-14(Wed)
	Copyright				: gaoguangtao
	Reference				: 
	======================================================================== */

#include "tlib.h"
#include <stdio.h>
#include "resource.h"
#include "ipmsg.h"
#include "msgstr.h"

//==================================================================
//函数名：  
//作者：    
//日期：    
//功能：    
//输入参数：
//返回值：  
//修改记录：
//==================================================================
MsgMng::MsgMng(ULONG nicAddr, int portNo, Cfg *_cfg)
{
	this->status = FALSE;
	this->packetNo = (ULONG)Time();

	this->udp_sd = INVALID_SOCKET;
	this->tcp_sd = INVALID_SOCKET;

	this->hAsyncWnd = 0;

	//local为自己定义的一个HostSub类型的数据，HostSub类型的数据保存用户名称，主机名称、主机端口、主机地址等信息。
	this->local.addr = nicAddr;
	this->local.portNo = htons(portNo); //将主机字节顺序转换为网络字节顺序
	this->cfg = _cfg;

	if (this->WSockInit(cfg ? TRUE : FALSE) == FALSE)
	{
		return;
	}

	DWORD size = sizeof(local.hostName);

	//获取当前计算机的名称
	if (::GetComputerName(local.hostName, &size) == FALSE)
	{
		GetLastErrorMsg("GetComputerName()");

		return;
	}

	if (nicAddr == INADDR_ANY)
	{
		char host[MAX_BUF];

		if (::gethostname(host, sizeof(host)) == -1)
		{
			strcpy(host, local.hostName);
		}

		//解析的域名或主机名来获取IP地址
		hostent	*ent = ::gethostbyname(host);

		if (ent)
		{
			local.addr = *(ULONG *)ent->h_addr_list[0];
		}
	}

	size = sizeof(local.userName);

	//获取当前登录用户的名称
	if (::GetUserName(local.userName, &size) != TRUE)
	{
		strncpyz(local.userName, NO_NAME, sizeof(local.userName));
	}

	status = TRUE;
}

MsgMng::~MsgMng()
{
	WSockTerm();
}


BOOL MsgMng::WSockInit(BOOL recv_flg)
{
	//用于保存Socket的版本信息
	WSADATA wsaData;

	if (::WSAStartup(0x0101, &wsaData) != 0)
	{
		return	GetSockErrorMsg("WSAStart()", NULL), FALSE;
	}

	//创建基于数据报的socket udp_sd;
	this->udp_sd = ::socket(AF_INET, SOCK_DGRAM, 0);

	if (this->udp_sd == INVALID_SOCKET)
	{
		return	GetSockErrorMsg("Please setup TCP/IP(controlpanel->network)\r\n"), FALSE;
	}

	if (recv_flg != TRUE)
	{
		return	TRUE;
	}
	
	//创建基于流的 socket tcp_sd;
	this->tcp_sd = ::socket(AF_INET, SOCK_STREAM, 0);
	
	if (this->tcp_sd == INVALID_SOCKET)
	{
		return	GetSockErrorMsg("Please setup2 TCP/IP(controlpanel->network)\r\n"), FALSE;
	}

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));

	addr.sin_family			= AF_INET;
	addr.sin_addr.s_addr	= this->local.addr;
	addr.sin_port			= this->local.portNo;

	//分别绑定套接字和本机地址，由于是用的不同通信协议(udp、tcp)，故相同的地址和端口可以绑定两次。

	if (::bind(this->udp_sd, (LPSOCKADDR)&addr, sizeof(addr)) != 0) //bind函数用于将套接字与指定端口相连
	{
		return	GetSockErrorMsg("bind()"), FALSE;
	}

	if (::bind(this->tcp_sd, (LPSOCKADDR)&addr, sizeof(addr)) != 0)
	{
		::closesocket(this->tcp_sd);

		this->tcp_sd = INVALID_SOCKET;

		GetSockErrorMsg("bind(tcp) error. Can't support file attach");
	}

	BOOL flg = TRUE;	// Non Block

	//把套接字设成非阻塞的模式
	if (::ioctlsocket(this->udp_sd, FIONBIO, (unsigned long *)&flg) != 0) //The ioctlsocket function controls the I/O mode of a socket.
	{
		return GetSockErrorMsg("ioctlsocket(nonblock)"), FALSE;
	}

	//把套接字设成非阻塞的模式
	if (IsAvailableTCP() && ::ioctlsocket(this->tcp_sd, FIONBIO, (unsigned long *)&flg) != 0)
	{
		return GetSockErrorMsg("ioctlsocket tcp(nonblock)"), FALSE;
	}

	flg = TRUE;			// allow broadcast

	//设置UDP套接字广播属性。
	if (::setsockopt(this->udp_sd, SOL_SOCKET, SO_BROADCAST, (char *)&flg, sizeof(flg)) != 0) 
	{
		return	GetSockErrorMsg("setsockopt(broadcast)"), FALSE;
	}

	int	buf_size = MAX_SOCKBUF;
	int buf_minsize = MAX_SOCKBUF / 2;		// UDP

	//设置发送缓冲区大小
	if (::setsockopt(this->udp_sd, SOL_SOCKET, SO_SNDBUF, (char *)&buf_size, sizeof(int)) != 0 
	    &&	::setsockopt(this->udp_sd, SOL_SOCKET, SO_SNDBUF, (char *)&buf_minsize, sizeof(int)) != 0) //
	{
		GetSockErrorMsg("setsockopt(sendbuf)");
	}

	buf_size = MAX_SOCKBUF;
	buf_minsize = MAX_SOCKBUF / 2;

	//设置接收缓冲区大小， 不明白下边的设置是什么意图
	if (::setsockopt(this->udp_sd, SOL_SOCKET, SO_RCVBUF, (char *)&buf_size, sizeof(int)) != 0 
	&&	::setsockopt(this->udp_sd, SOL_SOCKET, SO_RCVBUF, (char *)&buf_minsize, sizeof(int)) != 0)
	{
		GetSockErrorMsg("setsockopt(recvbuf)");
	}

	flg = TRUE;	// REUSE ADDR

	//设置地址重用，并且开始进行监听，看监听函数在这里。
	//端口复用 tcp 和udp 用同一个端口
	if (IsAvailableTCP() && ::setsockopt(this->tcp_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&flg, sizeof(flg)) != 0)
	{
		GetSockErrorMsg("setsockopt tcp(reuseaddr)");
	}

	if (IsAvailableTCP() && ::listen(this->tcp_sd, 5) != 0)
	{
		return	FALSE;
	}

	return	TRUE;
}


void MsgMng::WSockTerm(void)
{
	this->CloseSocket();

	if (IsNewShell())
	{
		WSACleanup();
	}
}

void MsgMng::CloseSocket(void)
{
	if (this->udp_sd != INVALID_SOCKET)
	{
		::closesocket(udp_sd);

		this->udp_sd = INVALID_SOCKET;
	}

	if (this->tcp_sd != INVALID_SOCKET)
	{
		::closesocket(tcp_sd);

		this->tcp_sd = INVALID_SOCKET;
	}
}

BOOL MsgMng::WSockReset(void)
{
	this->WSockTerm();

	return this->WSockInit(TRUE);
}

//向hostsub发送command和附加信息val
BOOL MsgMng::Send(HostSub *hostSub, ULONG command, int val)
{
	char buf[MAX_NAMEBUF];

	wsprintf(buf, "%d", val);

	return Send(hostSub->addr, hostSub->portNo, command, buf);
}

BOOL MsgMng::Send(HostSub *hostSub, ULONG command, const char *message, const char *exMsg)
{
	return Send(hostSub->addr, hostSub->portNo, command, message, exMsg);
}

BOOL MsgMng::Send(ULONG host, int port_no, ULONG command, const char *message, const char *exMsg)
{
	char buf[MAX_UDPBUF];
	int  trans_len;

	MakeMsg(buf, command, message, exMsg, &trans_len);

	return UdpSend(host, port_no, buf, trans_len);
}

//套接字的异步注册
BOOL MsgMng::AsyncSelectRegist(HWND hWnd)
{
	if (this->hAsyncWnd == 0)
	{
		this->hAsyncWnd = hWnd;
	}

	//WSAAsyncSelect()
	//简述:通知套接口有请求事件发生.
	//　   WSAAsyncSelect模型的优点:跟windows消息机制融合在一起，特别适合有界面的程序。
    //缺点：连接的客户端过多时，服务器性能下降。     
	//　        #include <winsock.h>
	//　        int PASCAL FAR WSAAsyncSelect ( SOCKET s, HWND hWnd,
	//　                        unsigned int wMsg, long lEvent );
	//　        
	//　        s 标识一个需要事件通知的套接口的描述符.
	//　        hWnd 标识一个在网络事件发生时需要接收消息的窗口句柄.
	//　        wMsg 在网络事件发生时要触发的消息.
	//　        lEvent 位屏蔽码,用于指明应用程序感兴趣的网络事件集合.
	//　        
	//　注释:
	//　        本函数用来请求Windows Sockets DLL为窗口句柄发一条消息－无论它何时检测到由lEvent参数指明的网络事件.
	//		  要发送的消息由wMsg参数标明.被通知的套接口由s标识.
	//　        本函数自动将套接口设置为非阻塞模式.
	//　        lEvent参数由下表中列出的值组成.
	//　        值 意义
	//　        FD_READ 欲接收读准备好的通知.
	//　        FD_WRITE 欲接收写准备好的通知.
	//　        FD_OOB 欲接收带边数据到达的通知.
	//　        FD_ACCEPT 欲接收将要连接的通知.
	//　        FD_CONNECT 欲接收已连接好的通知.
	//　        FD_CLOSE 欲接收套接口关闭的通知.
	//　        
	//　        启动一个WSAAsyncSelect()将使为同一个套接口启动的所有先前的WSAAsyncSelect()作废
	//requests Windows message-based notification of network events for a socket.
	if (::WSAAsyncSelect(udp_sd, hWnd, WM_UDPEVENT, FD_READ) == SOCKET_ERROR)
	{
		return FALSE;
	}

	//requests Windows message-based notification of network events for a socket.
	if (::WSAAsyncSelect(tcp_sd, hWnd, WM_TCPEVENT, FD_ACCEPT|FD_CLOSE) == SOCKET_ERROR)
	{
		return FALSE;
	}

	return TRUE;
}

BOOL MsgMng::Recv(MsgBuf *msg)
{
	RecvBuf buf;

	if (UdpRecv(&buf) != TRUE || buf.size == 0)
	{
		return FALSE;
	}

	return ResolveMsg(&buf, msg);
}

//根据参数创建一个消息包，buf保存了最后组装好的缓冲区，返回_packetNo
//这里的exmsg指的是诸如所有的文件名拼装而成的一个长字符串
ULONG MsgMng::MakeMsg(char *buf, int _packetNo, ULONG command, const char *msg, const char *exMsg, int *packet_len)
{
	int len;

	//strlen函数不包括字符串结束符‘\0’
	int ex_len = exMsg ? strlen(exMsg) + 1 : 0;
	int max_len = MAX_UDPBUF;

	if (packet_len == NULL)
	{
		packet_len = &len;
	}

//	%d 格式化输出短整形数据,TC环境中占用两个字节，输出整数范围为:32768~32767.Visual C++环境中占用四个字节，输出数据范围为:-2147483648~2147483647。
//  %u 格式化输出无符号短整形 
//  %ld 格式化输出长整形,一般占四个字节 
//  %c 输出字符型数据（单个字符），也可输出整型数据，范围为1~127 
//  %s 输出字符串 
	//buf的格式为：  ip版本：包的序号：本地的用户名：本地的主机名：命令：exmsg
	*packet_len = wsprintf(buf, "%d:%ld:%s:%s:%ld:", IPMSG_VERSION, _packetNo, local.userName, local.hostName, command);

	//如果packet的长度 +　ex_len超出了udp包的最大长度，则把ex_len设置为0
	if (ex_len + *packet_len + 1 >= MAX_UDPBUF)
	{
		ex_len = 0;
	}

	// max_len保存了剩余部分的最大长度
	max_len -= ex_len;

	// sprintf msg不是空，需要把msg加入到buf中，去掉msg中的\r
	if (msg != NULL)
	{
		*packet_len += LocalNewLineToUnix(msg, buf + *packet_len, max_len - *packet_len);
	}

	//为什么要++，因为最后多了一个\0
	(*packet_len)++;

	// 还有附加长度消息，把ex_msg的内容拷贝到buf中
	if (ex_len)
	{
		memcpy(buf + *packet_len, exMsg, ex_len);
		
		*packet_len += ex_len;
	}

	return _packetNo;
}

//去掉src中所有的\r符号，把剩余的部分保存到缓冲区dest中，返回转化的长度
int MsgMng::LocalNewLineToUnix(const char *src, char *dest, int maxlen)
{
	int		len = 0;

	maxlen--;// 去掉末尾的\0

	//如果src还没有到最后而且长度合法
	while (*src != '\0' && len < maxlen)
	{
		if ((dest[len] = *src++) != '\r')
		{
			len++;
		}
	}

	//最后加上结束符\0
	dest[len] = 0;

	return	len;
}

//把unix中的\n转化为本地windows系统中的\r\n
int MsgMng::UnixNewLineToLocal(const char *src, char *dest, int maxlen)
{
	int		len = 0;
	char	*tmpbuf = NULL;

	//如果起始方和目的方的地址相同，则首先把src拷贝到一个临时缓冲区tmpbuf中
	if (src == dest)
	{
		tmpbuf = _strdup(src), src = tmpbuf;
	}

	maxlen--;//去掉 \0所占用的位置

	while (*src != '\0' && len < maxlen)
	{
		if ((dest[len] = *src++) == '\n')
		{
			dest[len++] = '\r';
	
			if (len < maxlen)
			{
				dest[len] = '\n';
			}
		}

		len++;
	}

	//加上结束符\0
	dest[len] = 0;
	
	if (tmpbuf)
	{
		free(tmpbuf);
	}

	return	len;
}


//解析buf，结果保存到msg中
BOOL MsgMng::ResolveMsg(RecvBuf *buf, MsgBuf *msg)
{
	char	*exStr = NULL, *tok, *p;
	int		len;

	//msgBuf保存了消息的长度，exStr指向消息后面的附加信息的开始！！
	// buf->size 比strlen(buf->msgBuf)) + 1大说明有附加消息！！
	if (buf->size > (len = strlen(buf->msgBuf)) +1)
	{
		exStr = buf->msgBuf + len +1;
	}

	//msg->hostSub.addr保存了发送消息的主机的地址
	msg->hostSub.addr	= buf->addr.sin_addr.s_addr;
	msg->hostSub.portNo	= buf->addr.sin_port;

	//把第一个：之前的数据作为ip的version，保存到tok中
	if ((tok = separate_token(buf->msgBuf, ':', &p)) == NULL)
	{
		return	FALSE;
	}

	//如果version不是IPMSG_VERSION，直接返回，如果是，保存到msg的version中
	if ((msg->version = atoi(tok)) != IPMSG_VERSION)
	{
		return	FALSE;
	}

	//下一个：前面是packetno
	if ((tok = separate_token(NULL, ':', &p)) == NULL)
	{
		return	FALSE;
	}	
	msg->packetNo = atol(tok);

	//下一个：前面是用户名
	if ((tok = separate_token(NULL, ':', &p)) == NULL)
	{
		return	FALSE;
	}
	strncpyz(msg->hostSub.userName, tok, sizeof(msg->hostSub.userName));

	//下一个：前面是主机名
	if ((tok = separate_token(NULL, ':', &p)) == NULL)
	{
		return	FALSE;
	}
	strncpyz(msg->hostSub.hostName, tok, sizeof(msg->hostSub.hostName));

	//下一个：前面是命令
	if ((tok = separate_token(NULL, ':', &p)) == NULL)
	{
		return	FALSE;
	}	
	msg->command = atol(tok);

	int		cnt = 0, ex_len;
	*msg->msgBuf = 0;
	
	// 这个\0前面是消息的内容
	if ((tok = separate_token(NULL, 0, &p)) != NULL)
	{
		//把所有的\n转化为\r\n,保存到msg的msgBuf里面
		while (*tok != '\0' && cnt < MAX_UDPBUF -1)
		{
			if ((msg->msgBuf[cnt++] = *tok++) == '\n')
			{
				msg->msgBuf[cnt-1] = '\r';
	
				if (cnt < MAX_UDPBUF -1)
				{
					msg->msgBuf[cnt++] = '\n';
				}
			}
		}

		msg->msgBuf[cnt] = '\0';
	}

	//exOffset保存了附加消息的开始位置！！
	msg->exOffset = cnt;

	if (exStr && (ex_len = strlen(exStr) + 1) < MAX_UDPBUF -1)
	{
		//如果有附加消息，超出了最大值，则设置exOffset为MAX_UDPBUF - ex_len
		if (++msg->exOffset + ex_len >= MAX_UDPBUF)
		{
			msg->msgBuf[(msg->exOffset = MAX_UDPBUF - ex_len) -1] = '\0';
		}
		
		//在exOffset位置吧exStr拷贝到msg的msgBuf中
		memcpy(msg->msgBuf + msg->exOffset, exStr, ex_len);
	}

	return	TRUE;
}



BOOL MsgMng::UdpSend(ULONG host_addr, int port_no, const char *buf)
{
	return	UdpSend(host_addr, port_no, buf, strlen(buf) +1);
}

BOOL MsgMng::UdpSend(ULONG host_addr, int port_no, const char *buf, int len)
{
	struct sockaddr_in	addr;

	memset(&addr, 0, sizeof(addr));

	addr.sin_family			= AF_INET;
	addr.sin_port			= port_no;
	addr.sin_addr.s_addr	= host_addr;

	//sendto()适用于已连接的数据报或流式套接口发送数据。
	//对于数据报类套接口，必需注意发送数据长度不应超过通讯子网的IP包最大长度。
	//IP包最大长度在WSAStartup()调用返回的WSAData的iMaxUdpDg元素中。
	//如果数据太长无法自动通过下层协议，则返回WSAEMSGSIZE错误，数据不会被发送。
    //请注意成功地完成sendto()调用并不意味着数据传送到达。
	if (::sendto(udp_sd, buf, len, 0, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR)
	{
		switch (WSAGetLastError()) 
		{
			case WSAENETDOWN:
				break;
			case WSAEHOSTUNREACH:
				static	BOOL	done;
				if (done == FALSE) {
					done = TRUE;
	//				MessageBox(0, HOSTUNREACH_MSGSTR, inet_ntoa(*(LPIN_ADDR)&host_addr), MB_OK);
				}
				return	FALSE;
			default:
				return	FALSE;
		}


		if (WSockReset() != TRUE)
		{
			return	FALSE;
		}

		// 注册此窗口为异步选择窗口。在对socket消息的处理中应用了异步选择机制，使socket消息关联到特定的消息窗口。 
		if (hAsyncWnd && AsyncSelectRegist(hAsyncWnd) != TRUE)
		{
			return	FALSE;
		}

		if (::sendto(udp_sd, buf, len, 0, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR)
		{
			return	FALSE;
		}
	}

	return	TRUE;
}


BOOL MsgMng::UdpRecv(RecvBuf *buf)
{
	buf->addrSize = sizeof(buf->addr);

	// 
	//本函数由于从（已连接）套接口上接收数据，并捕获数据发送源的地址。
	//	对于SOCK_STREAM类型的套接口，最多可接收缓冲区大小个数据。
	//  如果套接口被设置为线内接收带外数据（选项为SO_OOBINLINE），且有带外数据未读入，则返回带外数据。
	//  应用程序可通过调用ioctlsocket()的SOCATMARK命令来确定是否有带外数据待读入。对于SOCK_STREAM类型套接口，忽略from和fromlen参数。
	//	对于数据报类套接口，队列中第一个数据报中的数据被解包，但最多不超过缓冲区的大小。
	//  如果数据报大于缓冲区，那么缓冲区中只有数据报的前面部分，其他的数据都丢失了，并且recvfrom()函数返回WSAEMSGSIZE错误。
	//在无连接的数据报socket方式下，由于本地socket并没有与远端机器建立连接，所以在发送数据时应指明目的地址
	buf->size = ::recvfrom(udp_sd,                  // 已连接套接字
		                   buf->msgBuf,             //接收数据缓冲区
						   sizeof(buf->msgBuf) -1,  //缓冲区长度
						   0,                       //调用操作方式
						   (LPSOCKADDR)&buf->addr,  //from （可选）装有源地址的缓冲区
						   &buf->addrSize);         //（可选）指向from缓冲区长度值

	if (buf->size == SOCKET_ERROR)
	{
		return	FALSE;
	}

	buf->msgBuf[buf->size] = 0; //消息结束符号

	return	TRUE;
}


BOOL MsgMng::Accept(HWND hWnd, ConnectInfo *info)
{
	struct sockaddr_in	addr;
	int		size = sizeof(addr), flg=TRUE;
	
	info->sd = ::accept(tcp_sd, (LPSOCKADDR)&addr, &size);

	if (info->sd == INVALID_SOCKET)
	{
		return	FALSE;
	}
	
	::setsockopt(info->sd, SOL_SOCKET, TCP_NODELAY, (char *)&flg, sizeof(flg));

	info->addr = addr.sin_addr.s_addr;
	info->port = addr.sin_port;
	info->server = info->complete = TRUE;

	for (int buf_size=cfg->TcpbufMax; buf_size > 0; buf_size /= 2)
	{
		//作用:设置发送缓冲区的大小，从最大值开始，如果失败，则把缓冲区大小减半再试
		//若无错误发生，setsockopt()返回0。否则的话，返回SOCKET_ERROR错误
		if (::setsockopt(info->sd, SOL_SOCKET, SO_SNDBUF, (char *)&buf_size, sizeof(buf_size)) == 0)
		{
			break;
		}
	}

	if (AsyncSelectConnect(hWnd, info))
	{
		info->startTick = info->lastTick = ::GetTickCount();

		return	TRUE;
	}

	::closesocket(info->sd);
	
	return	FALSE;
}

BOOL MsgMng::Connect(HWND hWnd, ConnectInfo *info)
{
	info->server = FALSE;
	
	if ((info->sd = ::socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
		return	FALSE;
	}

	BOOL	flg = TRUE;	// Non Block // 非阻塞
	
	//套接字控制 1 套接字 2 命令FIONBIO：允许或禁止非阻塞模式 3 非零 ：允许
	if (::ioctlsocket(info->sd, FIONBIO, (unsigned long *)&flg) != 0)
	{
		return	FALSE;
	}

	//设置套接字选项 在套接字级别设置选项 参数2为 SOL_SOCKET 
	//参数3 TCP_NODELAY选项禁止Nagle算法
	//	设置套接字info->sd 不允许目的方把数据包合并
	::setsockopt(info->sd, SOL_SOCKET, TCP_NODELAY, (char *)&flg, sizeof(flg));

	//设置套接字缓冲区大小
	for (int buf_size=cfg->TcpbufMax; buf_size > 0; buf_size /= 2)
	{
		if (::setsockopt(info->sd, SOL_SOCKET, SO_RCVBUF, (char *)&buf_size, sizeof(buf_size)) == 0)
		{
			break;
		}
	}

	if (AsyncSelectConnect(hWnd, info))
	{
		struct sockaddr_in	addr;

		memset(&addr, 0, sizeof(addr));

		addr.sin_family			= AF_INET;
		addr.sin_port			= info->port; //目的方端口
		addr.sin_addr.s_addr	= info->addr; //目的方地址

		info->complete = (::connect(info->sd, (LPSOCKADDR)&addr, sizeof(addr)) == 0);

		if (info->complete || WSAGetLastError() == WSAEWOULDBLOCK)
		{
			info->startTick = info->lastTick = ::GetTickCount();

			return	TRUE;
		}
	}

	::closesocket(info->sd);

	return	FALSE;
}

//关联hWnd和info结构，如果info是server，则监听read事件，如果info是客户端，则监听connect事件
//两个都监听close事件
BOOL MsgMng::AsyncSelectConnect(HWND hWnd, ConnectInfo *info)
{
	//// 用自定义的消息（特殊时间发生）通知特定的窗口处理socket消息
	int wsaAsyncSelectResult = ::WSAAsyncSelect(info->sd, hWnd, WM_TCPEVENT, (info->server ? FD_READ : FD_CONNECT)|FD_CLOSE);

	if (wsaAsyncSelectResult == SOCKET_ERROR)
	{
		return	FALSE;
	}
	
	return	TRUE;
}

BOOL MsgMng::ConnectDone(HWND hWnd, ConnectInfo *info)
{
	::WSAAsyncSelect(info->sd, hWnd, 0, 0);

	BOOL flg = FALSE;

	::ioctlsocket(info->sd, FIONBIO, (unsigned long *)&flg);

	return TRUE;
}